# -*- coding: utf-8 -*-

from hlm.node import *

from ip.and_gate import NodeAndGate
from ip.fabrics import ChannelStoreFabric


class NodeArbiterStore(Node):
    def initialize(self, params: ObjectDict) -> None:
        params.num_of_receivers: int
        # Create multiple instances receiver ports.
        self.port.receivers = [self.port_channel_receiver(ChannelStoreFabric)
                               for _ in range(params.num_of_receivers)]
        self.port.receiver_lanes = [self.port.receivers[ii].Bus
                                    for ii in range(params.num_of_receivers)]
        # Create the sender type for sending the data out.
        self.port.sender = self.port_channel_sender(ChannelStoreFabric)
        self.port.sender_lane = self.port.sender.Bus
        # Create the granted id register.
        self.reg.grant_id = 0
        # Create the halt detection node.
        self.node.halt_detect = self.node_create(
            NodeAndGate,
            {'num_of_ports': params.num_of_receivers})
        # Create the port to halt.
        self.port.halt_ins = [self.port_out(bool)
                              for _ in range(params.num_of_receivers)]
        # Connect the halt input signals.
        for ii in range(params.num_of_receivers):
            self.port.halt_ins[ii].connect(self.node.halt_detect.port.and_ins[ii])
        self.port.halt = self.port_bypass(self.node.halt_detect.port.and_out)

    tasks = ['task_halt_detection',
             'task_check_and_rotate']

    def task_halt_detection(self):
        # Loop and check whether it is empty, if any of them is empty, marks as
        # could shutdown.
        for ii in range(len(self.port.receivers)):
            self.port.halt_ins[ii].put(self.port.receiver_lanes[ii].is_any_empty())

    def task_check_and_rotate(self):
        # Modified from original arbiter algorithm.
        # Round-robin arbiter using double masked priority and extended width.

        # Get requests from the receivers.
        self.reg.requests = [False for _ in range(len(self.port.receivers))]
        self.reg.request_not_empty = False
        # Get requests from the receivers.
        for ii in range(len(self.port.receivers)):
            self.reg.requests[ii] = not self.port.receiver_lanes[ii].is_any_empty()
            self.reg.request_not_empty = self.reg.request_not_empty or self.reg.requests[ii]
        # Check the output port is full or not.
        if not self.reg.request_not_empty or self.port.sender_lane.is_any_full():
            return

        # Duplicate to double the request vector
        self.reg.requests += self.reg.requests
        # Get grand_id based on priority
        self.reg.grant_id_org = -1
        for ii in range(len(self.reg.requests)):
            if self.reg.requests[ii]:
                self.reg.grant_id_org = ii
                break
        # Mask the request for all bits to the opposite direction of the
        # priority
        self.reg.mask_f = [False for _ in range(self.reg.grant_id + 1)]
        self.reg.mask_t = [True for _ in range((len(self.reg.requests) - self.reg.grant_id - 1))]
        self.reg.mask = self.reg.mask_f + self.reg.mask_t

        # Generate masked request, perform priority encoding to obtain grant id
        self.reg.masked_request = [(x and y) for x, y in zip(self.reg.mask, self.reg.requests)]
        self.reg.grant_id_masked = -1
        for ii in range(len(self.reg.masked_request)):
            if self.reg.masked_request[ii]:
                self.reg.grant_id_masked = ii
                break
        # Calculate the granted ID.
        self.reg.grant_id = self.reg.grant_id_org if self.reg.grant_id_masked == self.reg.grant_id_org else self.reg.grant_id_masked
        # Modulo by vector size for wrap around index
        self.reg.grant_id %= len(self.port.receivers)
        # Grant and send the request
        self.port.sender_lane.put_bypass(self.port.receiver_lanes[self.reg.grant_id])
